/*
 * Copyright (c) 2010 Philip Foulkes <philip.foulkes@gmail.com>
 *
 * This file is free software and is made available under the 
 * terms of the GNU General Public License, version 2, or at your
 * option, any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/errno.h>
#include <linux/slab.h>

#include "mrp.h"
#include "mrp_private.h"
#include "mrp_state_machine.h"
#include "mrp_timer.h"

#include "mrp_leave_all.h"

static const int mrp_leave_all_state_table [MRP_PROTO_EVENT_NUM] [MRP_LEAVE_ALL_STATE_NUM] =
{
    [MRP_PROTO_EVENT_Begin]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_LEAVE_ALL_STATE_PASSIVE, MRP_PROTO_ACTION_START_leavealltimer),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_LEAVE_ALL_STATE_PASSIVE, MRP_PROTO_ACTION_START_leavealltimer) },
    [MRP_PROTO_EVENT_tx]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_LEAVE_ALL_STATE_PASSIVE, MRP_PROTO_ACTION_sLA),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_LEAVE_ALL_STATE_NO_CHANGE) },
    [MRP_PROTO_EVENT_rLA]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_LEAVE_ALL_STATE_PASSIVE, MRP_PROTO_ACTION_START_leavealltimer),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_LEAVE_ALL_STATE_PASSIVE, MRP_PROTO_ACTION_START_leavealltimer) },
    [MRP_PROTO_EVENT_leavealltimer]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_LEAVE_ALL_STATE_ACTIVE, MRP_PROTO_ACTION_START_leavealltimer),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_LEAVE_ALL_STATE_ACTIVE, MRP_PROTO_ACTION_START_leavealltimer) }
};

/**
*   Get the state based on the supplied event and state
*/
static int mrp_get_leave_all_state (int mrp_protocol_event, int mrp_leave_all_state, int * mrp_new_leave_all_state)
{
    int res;

    if ( (res = mrp_get_new_state(mrp_leave_all_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_leave_all_state,
            MRP_LEAVE_ALL_STATE_MIN, MRP_LEAVE_ALL_STATE_MAX, MRP_LEAVE_ALL_STATE_NO_CHANGE)) >= 0)
    {
        *mrp_new_leave_all_state = res;
        return 0;
    }
    
    return res;
}

/**
*   Get the protocol action based on the supplied event and state
*/
static int mrp_get_leave_all_protocol_action1 (int mrp_protocol_event, int mrp_leave_all_state, int * mrp_leave_all_protocol_action)
{
    int res;

    if ( (res = mrp_get_new_protocol_action1 (mrp_leave_all_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_leave_all_state,
            MRP_LEAVE_ALL_STATE_MIN, MRP_LEAVE_ALL_STATE_MAX)) >= 0)
    {
        *mrp_leave_all_protocol_action = res;
        return 0;
    }
    
    return res;
}


/**
*   Get the protocol action based on the supplied event and state
*/
static int mrp_get_leave_all_protocol_action2 (int mrp_protocol_event, int mrp_leave_all_state, int * mrp_leave_all_protocol_action)
{
    int res;

    if ( (res = mrp_get_new_protocol_action2 (mrp_leave_all_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_leave_all_state,
            MRP_LEAVE_ALL_STATE_MIN, MRP_LEAVE_ALL_STATE_MAX)) >= 0)
    {
        *mrp_leave_all_protocol_action = res;
        return 0;
    }
    
    return res;
}

/**
*   
*/
static int mrp_leave_all_start_leavealltimer (struct mrp_leave_all * mrp_leave_all)
{
    mrp_leave_all->leave_all_timer.expires = jiffies + MRP_TIMER_VALUE_LEAVE_ALL_TIME_JIFFIES;
    add_timer (&(mrp_leave_all->leave_all_timer));
    return 0;
}

static int mrp_leave_all_state_changed (struct mrp_leave_all * mrp_leave_all, int old_state, int new_state)
{
    if (new_state == MRP_LEAVE_ALL_STATE_ACTIVE)
        return mrp_perform_request_transmit_opportunity (mrp_leave_all->mrp, (struct mrp_state_machine *)mrp_leave_all);
    return 0;
}

static int mrp_leave_all_perform_protocol_action (struct mrp_leave_all * mrp_leave_all, int protocol_event)
{
    switch (protocol_event)
    {
        case MRP_PROTO_ACTION_sLA:
        {
            return mrp_perform_send_leave_all (mrp_leave_all->mrp);
        }
        case MRP_PROTO_ACTION_START_leavealltimer:
        {
            return mrp_leave_all_start_leavealltimer (mrp_leave_all);
        }
        default:
        {
            MRP_PRINT_INFO ("mrp_leave_all UNSUPPORTED PROTO ACTION\n");
            return -EINVAL;
        }
    }
    
    return 0;
}

static struct mrp_state_machine_ops mrp_leave_all_ops = {
    .mrp_state_machine_state_changed = (mrp_state_machine_state_changed) mrp_leave_all_state_changed,
    .mrp_state_machine_perform_protocol_action = (mrp_state_machine_perform_protocol_action) mrp_leave_all_perform_protocol_action,
    .mrp_state_machine_get_new_state = mrp_get_leave_all_state,
    .mrp_state_machine_get_new_protocol_action1 = mrp_get_leave_all_protocol_action1,
    .mrp_state_machine_get_new_protocol_action2 = mrp_get_leave_all_protocol_action2,
};

static void mrp_leave_all_timer_function (unsigned long data)
{
    mrp_leave_all_timer_expire_notify (((struct mrp_leave_all *) data)->mrp, (struct mrp_leave_all *) data);
}

/**
*   
*/
struct mrp_leave_all * mrp_leave_all_create_and_init (void)
{
    struct mrp_leave_all * mrp_leave_all;
    
    if ( 	! (mrp_leave_all = kzalloc (sizeof (struct mrp_leave_all), GFP_KERNEL)) 
        ||  mrp_state_machine_init (&mrp_leave_all->mrp_state_machine, MRP_LEAVE_ALL_STATE_PASSIVE, &mrp_leave_all_ops, "mrp_leave_all"))
    {
        mrp_leave_all_destroy (&mrp_leave_all);
        return NULL;
    }
    
    setup_timer (&(mrp_leave_all->leave_all_timer), mrp_leave_all_timer_function, (unsigned long) mrp_leave_all);
    
    return mrp_leave_all;
}

/**
*   
*/
void mrp_leave_all_destroy (struct mrp_leave_all ** mrp_leave_all)
{
    struct mrp_leave_all * l;
    
    l = *mrp_leave_all;
    
    if (l)
    {	
        del_timer_sync (&l->leave_all_timer);
        mrp_state_machine_clean_up (&l->mrp_state_machine);
        kfree (l);
        *mrp_leave_all = NULL;
    }
}

int mrp_leave_all_set_mrp (struct mrp_leave_all * mrp_leave_all, struct mrp * mrp)
{
    mrp_leave_all->mrp = mrp;
    return 0;
}

void mrp_leave_all_stop_leave_timer(struct mrp_leave_all * mrp_leave_all)
{
    del_timer_sync (&mrp_leave_all->leave_all_timer);
}
